package hg.framework.utils;

import com.csvreader.CsvReader;
import hg.framework.utils.config.HgFtpConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 工具类-文件操作
 * @author guoqifei
 * @since 2020-05-29
 */
@Slf4j
public class HgFileUtils {

    /**
     * 解析csv文件
     * <p>1、实体需实现序列化，即存在：serialVersionUID</p>
     * <p>2、实体需具有主键ID</p>
     * <p>3、默认使用GBK编码解析</p>
     * @param absFilePath 文件路径
     * @param clazz 对应实体类型
     * @param <T>
     * @return 解析结果
     */
    public static <T> List<T> parseCsvFile(String absFilePath, Class<T> clazz) {
        try {
            FileInputStream fileInputStream = new FileInputStream(absFilePath);
            CsvReader csvReader = new CsvReader(fileInputStream, ',', Charset.forName("GBK"));
            if (null == csvReader) {
                throw new Exception("文件解析错误");
            }
            String[] valArr = null;
            Field[] clazzFieldArr = clazz.getDeclaredFields();
            T t = null;
            List<T> dataList = new ArrayList<>();
            // 跳过表头
            csvReader.skipLine();
            int filedPropCcount = clazzFieldArr.length;
            while(csvReader.readRecord()) {
                valArr = csvReader.getValues();
                t = clazz.newInstance();
                for (int i = 0; i < valArr.length; i++) {
                    if (i + 2 >= filedPropCcount) {
                        // 超过实体属性长度，结束解析
                        break;
                    }
                    if (String.class != clazzFieldArr[i + 2].getType()) {
                        // 实体非字符串格式，结束解析
                        continue;
                    }
                    // 跳过serialVersionUID
                    // 跳过id
                    clazzFieldArr[i+2].setAccessible(true);
                    clazzFieldArr[i+2].set(t, HgStringUtils.val((valArr[i])));
                }
                dataList.add(t);
            }
            return dataList;
        } catch (Exception e) {
            log.error("parseCsvFile(),解析csv文件,errMsg:{}", e.getMessage(), e);
            return new ArrayList<>(0);
        }
    }

    /**
     * 解析excel文件
     * <p>1、实体需实现序列化，即存在：serialVersionUID</p>
     * <p>2、实体需具有主键ID</p>
     * @param absFilePath 文件路径
     * @param clazz 对应实体类型
     * @param <T>
     * @return 解析结果
     */
    public static <T> List<T> parseExcelFile(String absFilePath, Class<T> clazz) {
        try {
            FileInputStream fileInputStream = new FileInputStream(absFilePath);
            XSSFWorkbook workbook = new XSSFWorkbook(fileInputStream);
            XSSFSheet sheetAt = workbook.getSheetAt(0);
            // 每行记录
            XSSFRow itemRow = null;
            // 通过反射解析
            Field[] fields = clazz.getDeclaredFields();
            // 单元格记录
            XSSFCell itemCell = null;
            // 单元格记录，按String解析
            String itemCellVal = null;
            // 结果集合
            List<T> dataList = new ArrayList<>();
            DataFormatter dataFormatter = new DataFormatter();
            T t = null;
            // i = 1，表头不解析
            // i <= sheetAt.getLastRowNum(),最后一行可取等于
            for (int i = 1; i <= sheetAt.getLastRowNum(); i++) {
                itemRow = sheetAt.getRow(i);
                if (null == itemRow) {
                    continue;
                }
                t = clazz.newInstance();
                int filedPropCcount = fields.length;
                for (int j = 0; j < itemRow.getLastCellNum(); j++) {
                    if (j + 2 >= filedPropCcount) {
                        // 超过实体属性长度，结束解析
                        log.warn("parseExcelFile(),解析excel文件,warnMsg:超过实体属性长度，结束解析，filedPropCcount:{}", filedPropCcount);
                        break;
                    }
                    if (String.class != fields[j + 2].getType()) {
                        // 实体非字符串格式，结束解析
                        log.warn("parseExcelFile(),解析excel文件,warnMsg:实体非字符串格式，结束解析，propName:{},propType:{}", fields[j + 2].getName(), fields[j + 2].getType());
                        continue;
                    }
                    itemCell = itemRow.getCell(j);
                    // 使用dataFormatter进行格式处理
                    // 避免数据格式转换异常，eg: java.lang.IllegalStateException: Cannot get a STRING value from a NUMERIC cell
                    itemCellVal = (null == itemCell ? "" : dataFormatter.formatCellValue(itemCell));
                    itemCellVal = HgStringUtils.val(itemCellVal);
                    // 略过serialVersionUID
                    // 略过主键id
                    fields[j+2].setAccessible(true);
                    fields[j+2].set(t, itemCellVal);
                }
                dataList.add(t);
            }
            return dataList;
        } catch (Exception e) {
            log.error("parseExcelFile(),解析excel文件,errMsg:{}", e.getMessage(), e);
            return new ArrayList<>(0);
        }
    }

    private static String LOCAL_CHARSET = "GBK";
    // FTP协议里面，规定文件名编码为iso-8859-1
    private static String SERVER_CHARSET = "ISO-8859-1";
    /**
     * 下载FTP文件（指定目录）
     * <p>只下载文件，不会下载目录</p>
     * @param HgFtpConfig ftp连接配置
     * @param ftpDir 数据-ftp目录
     * @param localDir 本地目录
     * @return true: 下载成功
     */
    public static boolean downloadFileFromFtp(HgFtpConfig HgFtpConfig, String ftpDir, String localDir){
        log.info("downloadFileFromFtp(),下载FTP文件,HgFtpConfig:{}", HgFtpConfig);
        log.info("downloadFileFromFtp(),下载FTP文件,ftpDir:{}, localDir:{}", ftpDir, localDir);
        FTPClient ftpClient = null;
        try {
            ftpClient = new FTPClient();
            //ftpClient.setControlEncoding("GBK");
            ftpClient.connect(HgFtpConfig.getIp(), HgFtpConfig.getPort());
            ftpClient.login(HgFtpConfig.getUserName(), HgFtpConfig.getPassword());
            int replyCode = ftpClient.getReplyCode();
            if (!FTPReply.isPositiveCompletion(replyCode)) {
                throw new Exception("FTP登录失败");
            }
            if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(
                    "OPTS UTF8", "ON"))) {
                // 开启服务器对UTF-8的支持，如果服务器支持就用UTF-8编码，否则就使用本地编码（GBK）.
                LOCAL_CHARSET = "UTF-8";
            }
            ftpClient.setControlEncoding(LOCAL_CHARSET);
            //ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            //ftpClient.enterLocalPassiveMode();// 设置被动模式
            //切换FTP目录
            ftpClient.changeWorkingDirectory(ftpDir);
            FTPFile[] ftpFiles = ftpClient.listFiles();
            OutputStream os = null;
            String itemFileName = null;
            File itemLocalFile = null;
            String itemFtpFileName = null;
            for(FTPFile file : ftpFiles){
                log.info("downloadFileFromFtp(),下载FTP文件,fileName:{}", file.getName());
                if (file.isDirectory()) {
                    log.warn("downloadFileFromFtp(),下载FTP文件,warnMsg:目录，跳过");
                    continue;
                }
                //itemFileName = new String(file.getName().getBytes(SERVER_CHARSET), LOCAL_CHARSET);
                itemFileName = file.getName();
                //itemFileName = new String(file.getName().getBytes("GBK"), SERVER_CHARSET);
                itemLocalFile = new File(localDir + "/" + itemFileName);
                if (itemLocalFile.exists()) {
                    log.warn("downloadFileFromFtp(),下载FTP文件,warnMsg:文件已存在,不重复复制，跳过,fileName:{}", itemFileName);
                    continue;
                }
                os = new FileOutputStream(itemLocalFile);
                // 本地编码转FTP编码
                itemFtpFileName = new String(file.getName().getBytes(LOCAL_CHARSET), SERVER_CHARSET);
                //ftpClient.retrieveFile(file.getName(), os);
                ftpClient.retrieveFile(itemFtpFileName, os);
                os.flush();
                os.close();
            }
            ftpClient.logout();
            return true;
        } catch (Exception e) {
            log.error("copyFileFromFtp(),复制文件,errMsg:{}", e.getMessage(), e);
            return false;
        } finally {
            if (null != ftpClient && ftpClient.isConnected()) {
                try {
                    ftpClient.disconnect();
                } catch (IOException ioe) {
                    log.error("copyFileFromFtp(),复制文件-连接关闭失败,errMsg:{}", ioe.getMessage());
                }
            }
        }
    }

    /**
     * 验证目录是否存在，不存在则创建
     * @param dirPath 目录,eg:/opt/abc
     */
    public static void checkAndMkdir(String dirPath) {
        File fileDir = new File(dirPath);
        if (!fileDir.exists()) {
            log.info("checkAndMkdir(),检查目录是否存在,不存在-创建");
            fileDir.mkdirs();
        }
    }

    /**
     *
     * @param absFilePath csv路径
     * @param clazz 实体类
     * @param propMap 属性映射[key:表头名称,val:实体属性]
     * @param <T>
     * @return
     */
    public static <T> List<T> parseCsvFile(String absFilePath, Class<T> clazz, Map<String, String> propMap) {
        if (null == propMap || propMap.isEmpty()) {
            log.warn("parseCsvFile(),解析csv，warnMsg: 映射map为空，不执行");
            return null;
        }
        try {
            FileInputStream fileInputStream = new FileInputStream(absFilePath);
            CsvReader csvReader = new CsvReader(fileInputStream, ',', Charset.forName("GBK"));
            if (null == csvReader) {
                throw new Exception("文件解析错误");
            }
            // 读第一行
            if (!csvReader.readRecord()) {
                throw new Exception("数据为空");
            }
            // 表头
            final String[] valArr = csvReader.getValues();
            T t = null;
            List<T> dataList = new ArrayList<>();
            Map<String, Integer> propLocalMap = new HashMap<>(propMap.size());
            propMap.forEach((itemKey, itemVal) -> {
                // 列值坐标
                Integer propLocal = 0;
                for (int i = 0; i < valArr.length; i++) {
                    if(valArr[i] ==  itemKey) {
                        log.info("parseCsvFile(),解析csv，itemKey:{}，位置列:{}", itemKey, i);
                        propLocal = i;
                        break;
                    }
                }
                propLocalMap.put(itemKey, propLocal);
            });
            String[] dataArr = null;
            Integer itemPropLocal = null;
            String itemVal = null;
            Field itemField = null;
            while(csvReader.readRecord()) {
                dataArr = csvReader.getValues();
                t = clazz.newInstance();
                for (Map.Entry<String, String> itemData : propMap.entrySet()) {
                    itemPropLocal = propLocalMap.get(itemData.getKey());
                    if (null != itemPropLocal) {
                        // 列-值
                        itemVal = HgStringUtils.val(dataArr[itemPropLocal]);
                        itemField = clazz.getDeclaredField(itemData.getValue());
                        if (null != itemField) {
                            itemField.setAccessible(true);
                            itemField.set(t, itemVal);
                        }
                    }
                }
                HgBeanUtils.checkPropNull(t);
                dataList.add(t);
            }
            return dataList;
        } catch (Exception e) {
            log.error("parseCsvFile(),解析csv文件,errMsg:{}", e.getMessage(), e);
            return new ArrayList<>(0);
        }
    }
}
